﻿using System;
using System.Collections;
using System.Collections.Generic;
using MugenInjection.Core;
using MugenInjection.Core.Components;
using MugenInjection.Interface;
using MugenInjection.Interface.Components;

namespace MugenInjection.Infrastructure
{
    /// <summary>
    /// An internal container that manages and resolves components.
    /// </summary>
    public class ComponentContainer : DisposableObject, IComponentContainer
    {
        #region Fields

        private readonly List<IComponent> _allComponents = new List<IComponent>();

        private readonly CacheCollection<Type, List<IComponent>> _components =
            new CacheCollection<Type, List<IComponent>>();

        private readonly object _locker = new object();

        private IBindingActivatorComponent _bindingActivatorComponent;
        private IBindingManagerComponent _bindingManagerComponent;
        private IInjector _injector;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="ComponentContainer"/> class.
        /// </summary>
        public ComponentContainer(bool disposeComponentAfterUnload = true)
        {
            DisposeComponentAfterUnload = disposeComponentAfterUnload;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ComponentContainer"/> class.
        /// </summary>
        public ComponentContainer(IBindingActivatorComponent bindingActivatorComponent, IBindingManagerComponent bindingManagerComponent, bool disposeComponentAfterUnload = true)
            : this(disposeComponentAfterUnload)
        {
            Validate.ArgumentNotNull(bindingActivatorComponent, "bindingActivatorComponent");
            Validate.ArgumentNotNull(bindingManagerComponent, "bindingManagerComponent");
            _bindingActivatorComponent = bindingActivatorComponent;
            _bindingManagerComponent = bindingManagerComponent;
        }

        #endregion

        #region Implementation of IEnumerable

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>1</filterpriority>
        public IEnumerator<IComponent> GetEnumerator()
        {
            CheckInjector();
            lock (_locker)
            {
                return _allComponents.GetEnumerator();
            }
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region Method

        private void CreateSingleComponent()
        {
            var bindingActivatorComponent = _bindingActivatorComponent ?? new BindingActivatorComponent();
            var bindingManagerComponent = _bindingManagerComponent ?? new BindingManagerComponent();
            _bindingActivatorComponent = null;
            _bindingManagerComponent = null;
            BindingManagerComponent = bindingManagerComponent;
            BindingActivatorComponent = bindingActivatorComponent;
        }

        private void CheckInjector()
        {
            Validate.EnsureIsInitializedValue(_injector, "Injector");
        }

        private static void OnUnloadSingleComponent(IComponent component)
        {
            throw new NotSupportedException("You cannot unload the single components, they can only be replaced.");
        }

        private void UnloadComponent(IComponent component)
        {
            component.Unload();
            OnRemovedComponent(component);
            if (!DisposeComponentAfterUnload) return;
            component.Dispose();
        }

        private void OnAddedComponent(IComponent obj)
        {
            Action<IComponent> handler = AddedComponent;
            if (handler != null) handler(obj);
        }

        private void OnRemovedComponent(IComponent obj)
        {
            Action<IComponent> handler = RemovedComponent;
            if (handler != null) handler(obj);
        }

        #endregion

        #region Implementation of IComponentContainer

        /// <summary>
        /// Gets the number of elements contained in the <see cref="IBindingCollection"/>.
        /// </summary>
        /// <returns>
        /// The number of elements contained in the <see cref="IBindingCollection"/>.
        /// </returns>
        public int Count
        {
            get
            {
                return _allComponents.Count;
            }
        }

        /// <summary>
        /// Indicates that the component will be disposed after unload (Default is <value>true</value>).
        /// </summary>
        public bool DisposeComponentAfterUnload { get; set; }


        /// <summary>
        /// Binds a collection to the <see cref="IInjector"/>.
        /// </summary>
        /// <param name="injector">The specified <see cref="IInjector"/>.</param>
        public void Initialize(IInjector injector)
        {
            Validate.ArgumentNotNull(injector, "injector");
            lock (_locker)
            {
                if (_injector != null)
                    throw new NotSupportedException("The component container already has a injector associated with it!");
                if (injector.Components != this)
                    throw new NotSupportedException("The injector already has a component container associated with it!");
                _injector = injector;
                CreateSingleComponent();
            }
        }

        /// <summary>
        /// Adds the specified <see cref="IComponent"/>.
        /// </summary>
        /// <param name="component">The specified <see cref="IComponent"/>.</param>
        public void Add(IComponent component)
        {
            CheckInjector();
            Validate.ArgumentNotNull(component, "component");
            if (IsContain(component))
                throw new ArgumentException("The component is already in the container.");
            Type type = component.ComponentType;
            lock (_locker)
            {
                var components = _components.GetOrAdd(type, () => new List<IComponent>());
                if (component.IsSingle && components.Count != 0)
                    throw new ArgumentException(string.Format("Component of the type {0} is already registered. This component can be only one.", InjectorUtilsInternal.FormatType(type)));
                components.Add(component);
                _allComponents.Add(component);
            }
            component.Load(_injector, false);
            OnAddedComponent(component);
        }

        /// <summary>
        /// Removes the specified <see cref="IComponent"/>.
        /// </summary>
        /// <param name="component">The specified <see cref="IComponent"/>.</param>
        public bool Remove(IComponent component)
        {
            CheckInjector();
            Validate.ArgumentNotNull(component, "component");
            if (!IsContain(component)) return false;
            lock (_locker)
            {
                List<IComponent> components = _components[component.ComponentType];
                components.Remove(component);
                _allComponents.Remove(component);
                UnloadComponent(component);
            }
            return true;
        }

        /// <summary>
        /// Removes all components with the specified <see cref="Type"/>.
        /// </summary>
        /// <typeparam name="T">The specified <see cref="Type"/>.</typeparam>
        public void RemoveAll<T>() where T : IComponent
        {
            CheckInjector();
            lock (_locker)
            {
                List<IComponent> components = _components[typeof(T)];
                if (components == null) return;
                for (int index = 0; index < components.Count; index++)
                {
                    IComponent component = components[index];
                    _allComponents.Remove(component);
                    components.Remove(component);
                    UnloadComponent(component);
                    index--;
                }
            }
        }

        /// <summary>
        /// Gets the component with the specified <see cref="Type"/>.
        /// </summary>
        /// <typeparam name="T">The specified <see cref="Type"/>.</typeparam>
        /// <returns>An instance of <see cref="IComponent"/>.</returns>
        public T Get<T>() where T : IComponent
        {
            CheckInjector();
            lock (_locker)
            {
                List<IComponent> components = _components[typeof(T)];
                if (components == null || components.Count == 0)
                    throw new KeyNotFoundException(string.Format("The component of the type {0} is not registered.",
                                                                 InjectorUtilsInternal.FormatType(typeof(T))));
                return (T)components[0];
            }
        }

        /// <summary>
        /// Tries get the component with the specified <see cref="Type"/>.
        /// </summary>
        /// <typeparam name="T">The specified <see cref="Type"/>.</typeparam>
        /// <param name="component">An instance of <see cref="IComponent"/>.</param>
        /// <returns><c>True</c> if the component finds; otherwise, <c>false</c>.</returns>
        public bool TryGet<T>(out T component) where T : IComponent
        {
            CheckInjector();
            component = default(T);
            lock (_locker)
            {
                List<IComponent> components = _components[typeof(T)];
                if (components == null || components.Count == 0) return false;
                component = (T)components[0];
            }
            return true;
        }

        /// <summary>
        /// Indicates that the specified <see cref="Type"/> of component exists in the collection.
        /// </summary>
        /// <typeparam name="T">The specified component <see cref="Type"/>.</typeparam>
        /// <returns><c>True</c> if the specified component exist; otherwise, <c>false</c>.</returns>
        public bool IsExist<T>()
        {
            return IsExist(typeof(T));
        }

        /// <summary>
        /// Indicates that the specified <see cref="Type"/> of component exists in the collection.
        /// </summary>
        /// <param name="componentType">The specified component <see cref="Type"/>.</param>
        /// <returns><c>True</c> if the specified component exist; otherwise, <c>false</c>.</returns>
        public bool IsExist(Type componentType)
        {
            CheckInjector();
            Validate.ArgumentNotNull(componentType, "componentType");
            lock (_locker)
            {
                List<IComponent> components = _components[componentType];
                return components != null && components.Count != 0;
            }
        }

        /// <summary>
        /// Indicates that the <see cref="IComponent"/> component exists in the collection.
        /// </summary>
        /// <param name="component">The specified component <see cref="IComponent"/>.</param>
        /// <returns><c>True</c> if the specified component exist; otherwise, <c>false</c>.</returns>
        public bool IsContain(IComponent component)
        {
            CheckInjector();
            Validate.ArgumentNotNull(component, "component");
            lock (_locker)
            {
                return _allComponents.Contains(component);
            }
        }

        /// <summary>
        /// Gets all components with the specified <see cref="Type"/>.
        /// </summary>
        /// <typeparam name="T">The specified <see cref="Type"/>.</typeparam>
        /// <returns>An instances of <see cref="IComponent"/>.</returns>
        public IList<IComponent> GetAll<T>() where T : IComponent
        {
            CheckInjector();
            lock (_locker)
            {
                List<IComponent> components = _components[typeof(T)];
                if (components == null || components.Count == 0)
                    throw new KeyNotFoundException(string.Format("Component of the type {0} is not registered.",
                                                                 InjectorUtilsInternal.FormatType(typeof(T))));
                return components;
            }
        }

        /// <summary>
        /// Creates a new object that is a copy of the current components.
        /// </summary>
        /// <returns>A new object that is a copy of this instance.</returns>
        public IList<IComponent> CloneAll()
        {
            CheckInjector();
            var result = new IComponent[_allComponents.Count];
            lock (_locker)
            {
                for (int i = 0; i < _allComponents.Count; i++)
                {
                    IComponent allComponent = _allComponents[i];
                    result[i] = allComponent.Clone();
                }
            }
            return result;
        }

        /// <summary>
        /// Occurs after add specified <see cref="IComponent"/>.
        /// </summary>
        public event Action<IComponent> AddedComponent;

        /// <summary>
        /// Occurs after remove specified <see cref="IComponent"/>.
        /// </summary>
        public event Action<IComponent> RemovedComponent;

        #region Single components

        /// <summary>
        /// Gets or sets the specified <see cref="IBindingManagerComponent"/> for manage bindings.
        /// </summary>
        public IBindingManagerComponent BindingManagerComponent
        {
            get { return _bindingManagerComponent; }
            set
            {
                CheckInjector();
                Validate.PropertyNotNull(value, "BindingManagerComponent");
                if (_bindingManagerComponent != null)
                {
                    _bindingManagerComponent.Unloading -= OnUnloadSingleComponent;
                    UnloadComponent(_bindingManagerComponent);
                }
                _bindingManagerComponent = value;
                _bindingManagerComponent.Unloading += OnUnloadSingleComponent;
                _bindingManagerComponent.Load(_injector, false);
            }
        }

        /// <summary>
        /// Gets or sets the specified <see cref="IBindingActivatorComponent"/> for activate binding.
        /// </summary>
        public IBindingActivatorComponent BindingActivatorComponent
        {
            get { return _bindingActivatorComponent; }
            set
            {
                CheckInjector();
                Validate.PropertyNotNull(value, "BindingActivatorComponent");
                if (_bindingActivatorComponent != null)
                {
                    _bindingActivatorComponent.Unloading -= OnUnloadSingleComponent;
                    UnloadComponent(_bindingActivatorComponent);
                }
                _bindingActivatorComponent = value;
                _bindingActivatorComponent.Unloading += OnUnloadSingleComponent;
                _bindingActivatorComponent.Load(_injector, false);
            }
        }

        #endregion

        #endregion

        #region Overrides of DisposableObject

        /// <summary>
        /// Releases resources held by the object.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing && !IsDisposed)
            {
                for (int i = 0; i < _allComponents.Count; i++)
                {
                    Remove(_allComponents[i]);
                    i--;
                }

                if (AddedComponent != null)
                {
                    var delegates = new List<Delegate>(AddedComponent.GetInvocationList());
                    for (int i = 0; i < delegates.Count; i++)
                    {
                        var @delegate = delegates[i] as Action<IComponent>;
                        if (@delegate != null)
                            AddedComponent -= @delegate;
                        delegates.Remove(delegates[i]);
                        i--;
                    }
                }

                if (RemovedComponent != null)
                {
                    var delegates = new List<Delegate>(RemovedComponent.GetInvocationList());
                    for (int i = 0; i < delegates.Count; i++)
                    {
                        var @delegate = delegates[i] as Action<IComponent>;
                        if (@delegate != null)
                            RemovedComponent -= @delegate;
                        delegates.Remove(delegates[i]);
                        i--;
                    }
                }

            }
            base.Dispose(disposing);
        }

        #endregion
    }
}